<?php
class my_form_validation {
	protected $_field_data			= array();
	protected $_config_rules		= array();
	protected $_error_array			= array();
	protected $_error_messages		= array();
	protected $_error_prefix		= '<p>';
	protected $_error_suffix		= '</p>';
	protected $error_string			= '';
	protected $_safe_form_data		= FALSE;
	protected $controller           =null;
	protected $lang                 =array();
	//构造函数
	public function __construct($controller,$rules = array()){
		$this->lang=include('my_form_validation_lang.php');
		$this->controller=$controller;
		$this->_config_rules = $rules;
	}
	//设置验证规则  $validation->set_rules('username', '用户名', 'required');
	public function set_rules($field, $label = '', $rules = ''){
		// No reason to set rules if we have no POST data
		if (count($_POST) == 0) return $this;
		// 如果第一个参数传递一个数组则递归调用自己
		if (is_array($field)){
			foreach ($field as $row){
				// 如果没有设置 field 或 没有设置rules则跳过这条
				if ( ! isset($row['field']) OR ! isset($row['rules'])){
					continue;
				}
				// 如果$label没有传递，我们就使用$field
				$label = ( ! isset($row['label'])) ? $row['field'] : $row['label'];
				// Here we go!
				$this->set_rules($row['field'], $label, $row['rules']);
			}
			return $this;
		}
		// No fields? Nothing to do...
		if ( ! is_string($field) OR  ! is_string($rules) OR $field == ''){
			return $this;
		}
		// 如果$label没有传递，我们就使用$field
		$label = ($label == '') ? $field : $label;
		// 检测一下字段名是否为数组 通过[来判定
		// 如果是一个数组我们把它分成很多部分来处理
		if (strpos($field, '[') !== FALSE AND preg_match_all('/\[(.*?)\]/', $field, $matches)){
			// Note: Due to a bug in current() that affects some versions
			// of PHP we can not pass function call directly into it
			$x = explode('[', $field);
			$indexes[] = current($x);
			for ($i = 0; $i < count($matches['0']); $i++){
				if ($matches['1'][$i] != '') $indexes[] = $matches['1'][$i];
			}
			$is_array = TRUE;
		}else{
			$indexes	= array();
			$is_array	= FALSE;
		}
		// 建立主数组
		$this->_field_data[$field] = array(
			'field'				=> $field,
			'label'				=> $label,
			'rules'				=> $rules,
			'is_array'			=> $is_array,
			'keys'				=> $indexes,
			'postdata'			=> NULL,
			'error'				=> ''
		);
		return $this;
	}
	// 设置错误消息 键名需要与函数名一致
	public function set_message($lang, $val = ''){
		if ( ! is_array($lang)){
			$lang = array($lang => $val);
		}
		$this->_error_messages = array_merge($this->_error_messages, $lang);
		return $this;
	}
	//设置错误消息定界符 默认为p
	public function set_error_delimiters($prefix = '<p>', $suffix = '</p>'){
		$this->_error_prefix = $prefix;
		$this->_error_suffix = $suffix;
		return $this;
	}
	//获得某一字段的错误信息
	public function error($field = '', $prefix = '', $suffix = ''){
		if ( ! isset($this->_field_data[$field]['error']) OR $this->_field_data[$field]['error'] == ''){
			return '';
		}
		if ($prefix == ''){
			$prefix = $this->_error_prefix;
		}
		if ($suffix == ''){
			$suffix = $this->_error_suffix;
		}
		return $prefix.$this->_field_data[$field]['error'].$suffix;
	}
	//返回包裹在错误标签里的错误信息
	public function error_string($prefix = '', $suffix = ''){
		// 没有错误消息 验证通过
		if (count($this->_error_array) === 0) return '';
		if ($prefix == '') $prefix = $this->_error_prefix;
		if ($suffix == '') $suffix = $this->_error_suffix;
		// 生成错误字符串
		$str = '';
		foreach ($this->_error_array as $val){
			if ($val != '') $str .= $prefix.$val.$suffix."\n";
		}
		return $str;
	}
	//运行验证 这个函数承担了全部工作
	public function run($group = ''){
		// 没有post数据返回假
		if (count($_POST) == 0) return FALSE;
		//检查一下 $this->_field_data 有无验证规则 没有的话看一下 $this->_config_rules 有无验证规则
		//都没有的话返回假
		if (count($this->_field_data) == 0){
			if (count($this->_config_rules) == 0) return FALSE;
			$this->set_rules($this->_config_rules);
			// Is there a validation rule for the particular URI being accessed?
			if (count($this->_field_data) == 0){
				echo "Unable to find validation rules";
				return FALSE;
			}
		}
		// 加载错误消息语言包
		//$this->lang=include('form_validation_lang.php');
		// 开始验证
		foreach ($this->_field_data as $field => $row){
			//从 $_POST 中拿出数据
			if ($row['is_array'] == TRUE){
				$this->_field_data[$field]['postdata'] = $this->_reduce_array($_POST, $row['keys']);
			}else{
				if (isset($_POST[$field]) AND $_POST[$field] != ""){
					$this->_field_data[$field]['postdata'] = $_POST[$field];
				}
			}
			//开始验证
			$this->_execute($row, explode('|', $row['rules']), $this->_field_data[$field]['postdata']);
		}
		// 取得错误计数
		$total_errors = count($this->_error_array);
		if ($total_errors > 0) $this->_safe_form_data = TRUE;
		// Now we need to re-set the POST data with the new, processed data
		$this->_reset_post_array();
		// No errors, validation passes!
		if ($total_errors == 0) return TRUE;
		return FALSE;
	}
	protected function _reduce_array($array, $keys, $i = 0){
		if (is_array($array)){
			if (isset($keys[$i])){
				if (isset($array[$keys[$i]])){
					$array = $this->_reduce_array($array[$keys[$i]], $keys, ($i+1));
				}else{
					return NULL;
				}
			}else{
				return $array;
			}
		}
		return $array;
	}
	//用我们最终处理好的数据来填充$_POST
	protected function _reset_post_array(){
		foreach ($this->_field_data as $field => $row){
			if ( ! is_null($row['postdata'])){
				if ($row['is_array'] == FALSE){
					if (isset($_POST[$row['field']])){
						$_POST[$row['field']] = $this->prep_for_form($row['postdata']);
					}
				}else{
					// start with a reference
					$post_ref =& $_POST;
					// before we assign values, make a reference to the right POST key
					if (count($row['keys']) == 1){
						$post_ref =& $post_ref[current($row['keys'])];
					}else{
						foreach ($row['keys'] as $val){
							$post_ref =& $post_ref[$val];
						}
					}
					if (is_array($row['postdata'])){
						$array = array();
						foreach ($row['postdata'] as $k => $v){
							$array[$k] = $this->prep_for_form($v);
						}
						$post_ref = $array;
					}else{
						$post_ref = $this->prep_for_form($row['postdata']);
					}
				}
			}
		}
	}
	protected function _execute($row, $rules, $postdata = NULL, $cycles = 0){
		// If the $_POST data is an array we will run a recursive call
		if (is_array($postdata)){
			foreach ($postdata as $key => $val){
				$this->_execute($row, $rules, $val, $cycles);
				$cycles++;
			}
			return;
		}
		// If the field is blank, but NOT required, no further tests are necessary
		$callback = FALSE;
		if ( ! in_array('required', $rules) AND is_null($postdata)){
			// Before we bail out, does the rule contain a callback?
			if (preg_match("/(callback_\w+(\[.*?\])?)/", implode(' ', $rules), $match)){
				$callback = TRUE;
				$rules = (array('1' => $match[1]));
			}else{
				return;
			}
		}
		// Isset Test. Typically this rule will only apply to checkboxes.
		if (is_null($postdata) AND $callback == FALSE){
			if (in_array('isset', $rules, TRUE) OR in_array('required', $rules)){
				// 设置消息提示类型
				$type = (in_array('required', $rules)) ? 'required' : 'isset';
				if ( ! isset($this->_error_messages[$type])){
					if (FALSE === ($line = $this->lang[$type])) $line = 'The field was not set';
				}else{
					$line = $this->_error_messages[$type];
				}
				// 整合错误消息
				$message = sprintf($line, $this->_translate_fieldname($row['label']));
				// 保存错误消息
				$this->_field_data[$row['field']]['error'] = $message;
				if ( ! isset($this->_error_array[$row['field']])) $this->_error_array[$row['field']] = $message;
			}
			return;
		}
		// 循环匹配每条规则
		foreach ($rules As $rule){
			$_in_array = FALSE;
			if ($row['is_array'] == TRUE AND is_array($this->_field_data[$row['field']]['postdata'])){
				if ( ! isset($this->_field_data[$row['field']]['postdata'][$cycles])) continue;
				$postdata = $this->_field_data[$row['field']]['postdata'][$cycles];
				$_in_array = TRUE;
			}else{
				$postdata = $this->_field_data[$row['field']]['postdata'];
			}
			// 规则是否是一个callback函数
			$callback = FALSE;
			if (substr($rule, 0, 9) == 'callback_'){
				$rule = substr($rule, 9);
				$callback = TRUE;
			}
			// Strip the parameter (if exists) from the rule
			// Rules can contain a parameter: max_length[5]
			$param = FALSE;
			if (preg_match("/(.*?)\[(.*)\]/", $rule, $match)){
				$rule	= $match[1];
				$param	= $match[2];
			}
			// 调用相关回调函数 如果存在的话
			if ($callback === TRUE){
				//检查一下验证方法在控制器类中是否存在
				if ( ! method_exists($this->controller, $rule)) continue;
				// 运行函数取得结果
				$result = $this->controller->$rule($postdata, $param);
				// Re-assign the result to the master data array
				if ($_in_array == TRUE){
					$this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
				}else{
					$this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
				}
				// If the field isn't required and we just processed a callback we'll move on...
				if ( ! in_array('required', $rules, TRUE) AND $result !== FALSE) continue;
			}else{
				if ( ! method_exists($this, $rule)){
					//如果我们封装的函数里不存在这个回调函数，我们看一下php函数
					//用户可以使用php本地函数并传递一个参数
					if (function_exists($rule)){
						$result = $rule($postdata);
						if ($_in_array == TRUE){
							$this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
						}else{
							$this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
						}
					}else{
						echo "Unable to find validation rule: ".$rule;
					}
					continue;
				}
				$result = $this->$rule($postdata, $param);
				if ($_in_array == TRUE){
					$this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
				}else{
					$this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
				}
			}
			// 规则验证失败，捕获错误消息
			if ($result === FALSE){
				if ( ! isset($this->_error_messages[$rule])){
					if (FALSE === ($line = $this->lang[$rule])) $line = 'Unable to access an error message corresponding to your field name.';
				}else{
					$line = $this->_error_messages[$rule];
				}
				// Is the parameter we are inserting into the error message the name
				// of another field?  If so we need to grab its "field label"
				if (isset($this->_field_data[$param]) AND isset($this->_field_data[$param]['label'])){
					$param = $this->_translate_fieldname($this->_field_data[$param]['label']);
				}
				// 构建错误消息
				$message = sprintf($line, $this->_translate_fieldname($row['label']), $param);
				// Save the error message
				$this->_field_data[$row['field']]['error'] = $message;
				if ( ! isset($this->_error_array[$row['field']])){
					$this->_error_array[$row['field']] = $message;
				}
				return;
			}
		}
	}
	protected function _translate_fieldname($fieldname){
		// Do we need to translate the field name?
		// We look for the prefix lang: to determine this
		if (substr($fieldname, 0, 5) == 'lang:'){
			// Grab the variable
			$line = substr($fieldname, 5);
			// 能不能翻译 不能翻译返回原行
			if (FALSE === ($fieldname = $this->lang[$line])) return $line;
		}
		return $fieldname;
	}
	public function set_value($field = '', $default = ''){
		if ( ! isset($this->_field_data[$field])) return $default;
		// If the data is an array output them one at a time.
		//     E.g: form_input('name[]', set_value('name[]');
		if (is_array($this->_field_data[$field]['postdata'])){
			return array_shift($this->_field_data[$field]['postdata']);
		}
		return $this->_field_data[$field]['postdata'];
	}
	public function set_select($field = '', $value = '', $default = FALSE){
		if ( ! isset($this->_field_data[$field]) OR ! isset($this->_field_data[$field]['postdata'])){
			if ($default === TRUE AND count($this->_field_data) === 0) return ' selected="selected"';
			return '';
		}
		$field = $this->_field_data[$field]['postdata'];
		if (is_array($field)){
			if ( ! in_array($value, $field)) return '';
		}else{
			if (($field == '' OR $value == '') OR ($field != $value)) return '';
		}
		return ' selected="selected"';
	}
	public function set_radio($field = '', $value = '', $default = FALSE){
		if ( ! isset($this->_field_data[$field]) OR ! isset($this->_field_data[$field]['postdata'])){
			if ($default === TRUE AND count($this->_field_data) === 0){
				return ' checked="checked"';
			}
			return '';
		}
		$field = $this->_field_data[$field]['postdata'];
		if (is_array($field)){
			if ( ! in_array($value, $field)) return '';
		}else{
			if (($field == '' OR $value == '') OR ($field != $value)) return '';
		}
		return ' checked="checked"';
	}
	public function set_checkbox($field = '', $value = '', $default = FALSE){
		if ( ! isset($this->_field_data[$field]) OR ! isset($this->_field_data[$field]['postdata'])){
			if ($default === TRUE AND count($this->_field_data) === 0){
				return ' checked="checked"';
			}
			return '';
		}
		$field = $this->_field_data[$field]['postdata'];
		if (is_array($field)){
			if ( ! in_array($value, $field)) return '';
		}else{
			if (($field == '' OR $value == '') OR ($field != $value)) return '';
		}
		return ' checked="checked"';
	}
	public function required($str){
		if ( ! is_array($str)){
			return (trim($str) == '') ? FALSE : TRUE;
		}else{
			return ( ! empty($str));
		}
	}
	public function regex_match($str, $regex){
		if ( ! preg_match($regex, $str)) return FALSE;
		return  TRUE;
	}
	public function matches($str, $field){
		if ( ! isset($_POST[$field])){
			return FALSE;
		}
		$field = $_POST[$field];
		return ($str !== $field) ? FALSE : TRUE;
	}
	//这里需要一个数据库链接
	public function is_unique($str, $field){
		return true;
    }
	public function min_length($str, $val){
		if (preg_match("/[^0-9]/", $val)){
			return FALSE;
		}
		if (function_exists('mb_strlen')){
			return (mb_strlen($str) < $val) ? FALSE : TRUE;
		}
		return (strlen($str) < $val) ? FALSE : TRUE;
	}
	public function max_length($str, $val){
		if (preg_match("/[^0-9]/", $val)) return FALSE;
		if (function_exists('mb_strlen')){
			return (mb_strlen($str) > $val) ? FALSE : TRUE;
		}
		return (strlen($str) > $val) ? FALSE : TRUE;
	}
	public function exact_length($str, $val){
		if (preg_match("/[^0-9]/", $val)) return FALSE;
		if (function_exists('mb_strlen')){
			return (mb_strlen($str) != $val) ? FALSE : TRUE;
		}
		return (strlen($str) != $val) ? FALSE : TRUE;
	}
	public function valid_email($str){
		return ( ! preg_match("/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix", $str)) ? FALSE : TRUE;
	}
	public function valid_emails($str){
		if (strpos($str, ',') === FALSE) return $this->valid_email(trim($str));
		foreach (explode(',', $str) as $email){
			if (trim($email) != '' && $this->valid_email(trim($email)) === FALSE){
				return FALSE;
			}
		}
		return TRUE;
	}
	public function valid_ip($ip){
		return $this->controller->input->valid_ip($ip);
	}
	public function alpha($str){
		return ( ! preg_match("/^([a-z])+$/i", $str)) ? FALSE : TRUE;
	}
	public function alpha_numeric($str){
		return ( ! preg_match("/^([a-z0-9])+$/i", $str)) ? FALSE : TRUE;
	}
	public function alpha_dash($str){
		return ( ! preg_match("/^([-a-z0-9_-])+$/i", $str)) ? FALSE : TRUE;
	}
	public function numeric($str){
		return (bool)preg_match( '/^[\-+]?[0-9]*\.?[0-9]+$/', $str);
	}
	public function is_numeric($str){
		return ( ! is_numeric($str)) ? FALSE : TRUE;
	}
	public function integer($str){
		return (bool) preg_match('/^[\-+]?[0-9]+$/', $str);
	}
	public function decimal($str){
		return (bool) preg_match('/^[\-+]?[0-9]+\.[0-9]+$/', $str);
	}
	public function greater_than($str, $min){
		if ( ! is_numeric($str)){
			return FALSE;
		}
		return $str > $min;
	}
	public function less_than($str, $max){
		if ( ! is_numeric($str)){
			return FALSE;
		}
		return $str < $max;
	}
	public function is_natural($str){
		return (bool) preg_match( '/^[0-9]+$/', $str);
	}
	public function is_natural_no_zero($str){
		if ( ! preg_match( '/^[0-9]+$/', $str)){
			return FALSE;
		}
		if ($str == 0){
			return FALSE;
		}
		return TRUE;
	}
	public function valid_base64($str){
		return (bool) ! preg_match('/[^a-zA-Z0-9\/\+=]/', $str);
	}
	public function prep_for_form($data = ''){
		if (is_array($data)){
			foreach ($data as $key => $val){
				$data[$key] = $this->prep_for_form($val);
			}
			return $data;
		}
		if ($this->_safe_form_data == FALSE OR $data === ''){
			return $data;
		}
		return str_replace(array("'", '"', '<', '>'), array("&#39;", "&quot;", '&lt;', '&gt;'), stripslashes($data));
	}
	public function prep_url($str = ''){
		if ($str == 'http://' OR $str == ''){
			return '';
		}
		if (substr($str, 0, 7) != 'http://' && substr($str, 0, 8) != 'https://'){
			$str = 'http://'.$str;
		}
		return $str;
	}
	public function strip_image_tags($str){
		return $this->controller->input->strip_image_tags($str);
	}
	public function xss_clean($str){
		return $this->controller->security->xss_clean($str);
	}
	public function encode_php_tags($str){
		return str_replace(array('<?php', '<?PHP', '<?', '?>'),  array('&lt;?php', '&lt;?PHP', '&lt;?', '?&gt;'), $str);
	}
//
}